
// sys
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
// 3rd-party
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/bufferevent.h>
// mine
#include "listener.h"
#include "tcp_session.h"
#include "exception.h"

namespace net_base {
IMPL_TYPE(Listener) {
  evconnlistener* lev;
  event_base* evbase;
  NewConnCB new_conn_cb;

  IMPL_NAME(event_base* evb) : lev(nullptr), evbase(evb) {}
  ~IMPL_NAME(void) {
    if (lev)
      evconnlistener_free(lev);
  }
};
} // namespace net_base

namespace {

void OnAccept(evconnlistener* lev, evutil_socket_t fd, sockaddr* sin, 
              int socklen, void* arg) {
  using namespace net_base;
  IMPL_CAST(Listener, impl, arg);
  bufferevent* bev = bufferevent_socket_new(impl->evbase, fd, 
                                            BEV_OPT_CLOSE_ON_FREE|
                                            BEV_OPT_THREADSAFE|
                                            BEV_OPT_DEFER_CALLBACKS);
  if (!bev) {
    __Internal_Log("OnAccept: failed to create bev, fd(%d)", fd);
    return; 
  }

  (impl->new_conn_cb)(TcpSessionPtr(new TcpSession(bev)));
}

void OnError(struct evconnlistener* lev, void* arg) {
  using namespace net_base;
  evutil_socket_t fd = evconnlistener_get_fd(lev);
  sockaddr_in sin;
  socklen_t len = sizeof(sin);
  char ip[16] = {0};
  if (0 == getsockname(fd, (sockaddr*)&sin, &len) && 
      NULL != evutil_inet_ntop(AF_INET, &sin.sin_addr, ip, 16)) {
    __Internal_Log("error occur at listener(%s:%u)", ip, sin.sin_port);
  } else {
    char buffer[512] = {0};
    __Internal_Log("error occur at listener(%s)", strerror_r(errno, buffer, 512));
  }

  if (lev) {
    evconnlistener_free(lev);
    IMPL_CAST(Listener, impl, arg);
    impl->lev = nullptr;
  }
}

}

namespace net_base {
Listener::Listener(event_base* evbase) 
  : IMPL_INIT(evbase) {
}

Listener::~Listener(void) {
  IMPL_DELETE;
}

void Listener::Listen(const char* ip, uint16_t port, const NewConnCB& cb) {
  if (!cb) return;

  sockaddr_in sin;
  memset(&sin, 0, sizeof(sin));
  if (1 != evutil_inet_pton(AF_INET, ip, &sin.sin_addr))
    throw sysapi_error(errno);

  sin.sin_family = AF_INET;
  sin.sin_port = htons(port);

  Listen((sockaddr*)&sin, sizeof(sin), cb);
}

void Listener::Listen(const char* ip_port, const NewConnCB& cb) {
  sockaddr_in sin;
  int len = sizeof(sin);
  if (0 != evutil_parse_sockaddr_port(ip_port, (sockaddr*)&sin, &len))
    throw parse_ip_port_error(ip_port);
  Listen((sockaddr*)&sin, len, cb);
}

void Listener::Listen(const sockaddr* sa, int socklen, const NewConnCB& cb) {
  IMPL.new_conn_cb = cb;
  IMPL.lev = evconnlistener_new_bind(IMPL.evbase, OnAccept, &IMPL, 
                                     LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|
                                     LEV_OPT_THREADSAFE,
                                     -1, sa, socklen);
  if (!IMPL.lev) throw create_listener_failed();
  evconnlistener_set_error_cb(IMPL.lev, OnError);
}


} // namespace net_base

